﻿
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace CatEars.Core.FileFormat.Csv
{
    /// <summary>
    /// CsvCore类
    /// </summary>
    public static class CsvCore
    {
        #region 写入方法

        /// <summary>
        /// 向流中写入一行csv行
        /// </summary>
        /// <param name="csvDefine">csv格式定义 不能为null</param>
        /// <param name="sw">StreamWriter流对象</param>
        /// <param name="fieldColl">字段列表</param>
        /// <param name="blnAutoFlush">是否调用sw.Flush()方法</param>
        public static void WriteOneCsvRow(
            CsvDefine csvDefine,
            StreamWriter sw,
            IEnumerable<string> fieldColl,
            bool blnAutoFlush)
        {
            if (fieldColl == null)
            {
                return;
            }
            StringBuilder strBuilder = new StringBuilder();
            AppendOneCsvRow(csvDefine, strBuilder, fieldColl);
            if (strBuilder.Length == 0)
            {
                return;
            }
            strBuilder.Append(csvDefine.NewLine);
            sw.Write(strBuilder.ToString());
            if (blnAutoFlush && !sw.AutoFlush)
            {
                sw.Flush();
            }
        }

        /// <summary>
        /// 生成一行csv行
        /// </summary>
        /// <param name="csvDefine">csv格式定义 不能为null</param>
        /// <param name="strBuilder">strBuilder对象</param>
        /// <param name="fieldColl">字段列表</param>
        public static void AppendOneCsvRow(
            CsvDefine csvDefine,
            StringBuilder strBuilder,
            IEnumerable<string> fieldColl)
        {
            foreach (string strCell in fieldColl)
            {
                AppendFormatField(csvDefine, strBuilder, strCell);
                strBuilder.Append(csvDefine.SplitChar);
            }
            if (strBuilder.Length != 0)
            {
                strBuilder.Length = strBuilder.Length - 1;
            }
        }

        /// <summary>
        /// 格式化单元格内容并添加到Append到strBuilder中
        /// </summary>
        /// <param name="csvDefine">csv格式定义 不能为null</param>
        /// <param name="strBuilder">Csv行缓存</param>
        /// <param name="strValue">当前单元格</param>
        private static void AppendFormatField(
            CsvDefine csvDefine,
            StringBuilder strBuilder,
            string strValue)
        {
            if (strValue == null)
            {
                strValue = "";
            }
            int intStartIndex = strBuilder.Length;
            bool blnNeedBorder = csvDefine.WriteBorderForAllCell;
            if (csvDefine.ReplaceNewLineAtWrite != null)
            {
                StringBuilder strValue0 = new StringBuilder();
                foreach (var ch in strValue)
                {
                    if (ch == '\r')
                    {
                        continue;
                    }
                    else if (ch == '\n')
                    {
                        strValue0.Append(csvDefine.ReplaceNewLineAtWrite);
                    }
                    else
                    {
                        strValue0.Append(ch);
                    }
                }
                strValue = strValue0.ToString();
            }
            foreach (var ch in strValue)
            {
                if (ch == csvDefine.BorderChar || ch == csvDefine.EscapeChar)
                {
                    strBuilder.Append(csvDefine.EscapeChar);
                    blnNeedBorder = true;
                }
                if (!blnNeedBorder)
                {
                    if (ch == '\n' || ch == '\r'
                        || ch == csvDefine.SplitChar)
                    {
                        blnNeedBorder = true;
                    }
                }
                strBuilder.Append(ch);
            }
            if (blnNeedBorder)
            {
                strBuilder.Insert(intStartIndex, csvDefine.BorderChar);
                strBuilder.Append(csvDefine.BorderChar);
            }
        }

        #endregion

        #region 读取方法

        /// <summary>
        /// 从CSV文件中读取一行
        /// </summary>
        /// <param name="csvDefine">csv格式定义 不能为null</param>
        /// <param name="sr">StreamReader流对象</param>
        /// <returns>csv行</returns>
        public static string[] ReadOneCsvRow(
            CsvDefine csvDefine,
            StreamReader sr)
        {
            int intEndFlag;
            return ReadOneCsvRow(csvDefine, sr, out intEndFlag);
        }

        /// <summary>
        /// 从CSV文件中读取一行
        /// </summary>
        /// <param name="csvDefine">csv格式定义 不能为null</param>
        /// <param name="sr">StreamReader流对象</param>
        /// <param name="intEndFlag">结束标识 0正常行结束 1文件末尾</param>
        /// <returns>csv行</returns>
        public static string[] ReadOneCsvRow(
            CsvDefine csvDefine,
            StreamReader sr,
            out int intEndFlag)
        {
            intEndFlag = 0;
            int intBufferLen = 1;
            char[] chrBuffer = new char[intBufferLen];
            bool blnBorderedCell = false;
        StartRead:
            StringBuilder strCellBuffer = new StringBuilder();
            LinkedList<string> rowCache = new LinkedList<string>();
            int intReadCharCount = 0;

            //两种单元格（A:带边界符和B:不带边界）
            //A+B,A+A,B+A 间必须有 分隔符 或 换行 隔开，否则2个单元格粘在一起，视为错误格式
            //满足以下2个条件即可解决问题
            //B+A A+A检查方法：进入接收A模式时，strCellBuffer必须为空
            //A+B检查方法：记录上一个字符,若接收第一个字符给B，则chrLast必须是“分隔符 或 换行”。
            char chrLast = '\n';
            //这里复制为\n假设前面一个字符是换行
            char chrCurrent = '\n';
            while (true)
            {
                int intRead = sr.Read(chrBuffer, 0, intBufferLen);
                if (intRead == 0)
                {
                    //当前行结束
                    rowCache.AddLast(strCellBuffer.ToString());
                    strCellBuffer.Clear();
                    //文件结束
                    goto EndOfRead;
                }
                intReadCharCount++;
                chrLast = chrCurrent;
                chrCurrent = chrBuffer[0];
                if (chrCurrent == csvDefine.SplitChar)
                {
                    //前一单元格结束
                    rowCache.AddLast(strCellBuffer.ToString());
                    strCellBuffer.Clear();
                }
                else if (chrCurrent == csvDefine.BorderChar)
                {
                    if (strCellBuffer.Length > 0)
                    {
                        //进入该模式时候strCellBuffer必须为空，否则视为格式错误
                        string strNext = ReadNextString(sr, 100);
                        throw new FormatException(
                            string.Format("“{0}” | “{1}{2}”附近有格式错误", strCellBuffer, chrCurrent, strNext));
                    }
                    blnBorderedCell = true;
                    //进入带边界字符的单元格
                    bool blnEscapeFlag = false;
                    while (true)
                    {
                        int intRead2 = sr.Read(chrBuffer, 0, intBufferLen);
                        if (intRead2 == 0)
                        {
                            //当前行结束
                            rowCache.AddLast(strCellBuffer.ToString());
                            strCellBuffer.Clear();
                            //没有接收到单元格边界符就结束,属于格式错误的情况
                            goto EndOfRead;
                        }
                        intReadCharCount++;
                        chrLast = chrCurrent;
                        chrCurrent = chrBuffer[0];
                        if (blnEscapeFlag)
                        {
                            //被转义字符加入单元格
                            strCellBuffer.Append(chrCurrent);
                            blnEscapeFlag = false;
                        }
                        else if (chrCurrent == csvDefine.EscapeChar)
                        {
                            if (chrCurrent == csvDefine.BorderChar)
                            {
                                //转义字符和边界符，需要判断下一个字符是不是csvDefine.SplitChar \r \n
                                int intNextChar = sr.Peek();
                                if (intNextChar == csvDefine.SplitChar
                                    || intNextChar == -1
                                    || intNextChar == '\r'
                                    || intNextChar == '\n')
                                {
                                    //由下一个循环对单元格进行终结
                                    //rowCache.AddLast(strCellBuffer.ToString());
                                    //strCellBuffer.Clear();
                                    break;
                                }
                            }
                            blnEscapeFlag = true;
                        }
                        else if (chrCurrent == csvDefine.BorderChar)
                        {
                            //转义字符和边界符不一样的情况下进入该分支
                            //单元格结束
                            //由下一个循环对单元格进行终结
                            //rowCache.AddLast(strCellBuffer.ToString());
                            //strCellBuffer.Clear();
                            break;
                        }
                        else
                        {
                            //单元格内容
                            strCellBuffer.Append(chrCurrent);
                        }
                    }
                }
                else if (chrCurrent == '\n')
                {
                    //前一单元格结束
                    rowCache.AddLast(strCellBuffer.ToString());
                    strCellBuffer.Clear();
                    //当前行也结束
                    break;
                }
                else if (chrCurrent == '\r')
                {
                    //忽略\r符
                }
                else
                {
                    //一般的单元格内容
                    if (strCellBuffer.Length == 0)
                    {
                        if (chrLast != '\n'
                            && chrLast != csvDefine.BorderChar
                            && chrLast != csvDefine.SplitChar)
                        {
                            //普通单元格进入之前若前面一个字符非以上字符，则视为格式错误
                            string strNext = ReadNextString(sr, 100);
                            throw new FormatException(
                                string.Format("“{0}{1}{2}”附近有格式错误", chrLast, chrCurrent, strNext));
                        }
                    }
                    strCellBuffer.Append(chrCurrent);
                }
            }
        EndOfRead:
            if (sr.EndOfStream)
            {
                intEndFlag = 1;
            }
            if (intReadCharCount == 0)
            {
                //读取到最后的空行，直接返回null
                return null;
            }
            if (csvDefine.IgnoreEmptyRowAtRead && !blnBorderedCell)
            {
                //blnBorderedCell:有种空行是带边界符的，这种不忽略
                if (rowCache.Count == 0
                    || (rowCache.Count == 1 && string.IsNullOrEmpty(rowCache.First.Value)))
                {
                    goto StartRead;
                }
            }
            return rowCache.ToArray();
        }

        /// <summary>
        /// 完后读n个字符
        /// </summary>
        /// <param name="sr">StreamReader流对象</param>
        /// <param name="intCharCount">要读取的字符数</param>
        /// <returns>字符串</returns>
        private static string ReadNextString(
            StreamReader sr, int intCharCount)
        {
            char[] chrBuffer = new char[intCharCount];
            int intReadCount = sr.Read(chrBuffer, 0, intCharCount);
            return new string(chrBuffer, 0, intReadCount);
        }

        /// <summary>
        /// 从CSV文件中读取intRowCount行
        /// </summary>
        /// <param name="csvDefine">csv格式定义 不能为null</param>
        /// <param name="sr">StreamReader流对象</param>
        /// <param name="intRowCount">要读取的行数</param>
        /// <returns>csv行</returns>
        public static IEnumerable<string[]> ReadCsvRow(
            CsvDefine csvDefine,
            StreamReader sr,
            int intRowCount)
        {
            int intEndFlag;
            int intRowCount0 = 0;
            if (intRowCount <= 0)
            {
                yield break;
            }
            while (true)
            {
                var row = ReadOneCsvRow(csvDefine, sr, out intEndFlag);
                if (row != null)
                {
                    yield return row;
                    intRowCount0++;
                    if (intRowCount0 >= intRowCount)
                    {
                        break;
                    }
                }
                if (intEndFlag == 1)
                {
                    break;
                }
            }
        }

        /// <summary>
        /// 读取CSV文件,逐行返回
        /// </summary>
        /// <param name="csvDefine">csv格式定义 不能为null</param>
        /// <param name="sr">StreamReader流对象</param>
        /// <returns>csv数据</returns>
        public static IEnumerable<string[]> ReadCsvToEnd(
            CsvDefine csvDefine,
            StreamReader sr)
        {
            int intEndFlag;
            while (true)
            {
                var row = ReadOneCsvRow(csvDefine, sr, out intEndFlag);
                if (row != null)
                {
                    yield return row;
                }
                if (intEndFlag == 1)
                {
                    break;
                }
            }
        }

        #endregion

        #region 其它功能
        
        /// <summary>
        /// 将多个字段组成一个csv行
        /// </summary>
        /// <param name="csvDefine">csv格式定义</param>
        /// <param name="fieldColl">字段列表 不能为null</param>
        /// <returns>组合后的字符串</returns>
        public static string CombineToCsvRow(
            CsvDefine csvDefine,
            IEnumerable<string> fieldColl)
        {
            if (csvDefine == null)
            {
                csvDefine = CsvDefine.Default;
            }
            StringBuilder strB = new StringBuilder();
            CsvCore.AppendOneCsvRow(csvDefine, strB, fieldColl);
            return strB.ToString();
        }

        /// <summary>
        /// 将一个字符串拆分为
        /// </summary>
        /// <param name="csvDefine">csv格式定义</param>
        /// <param name="strCsvLine">一个csv行</param>
        /// <returns>拆分后的csv行</returns>
        public static string[] SplitFromCsvRow(
            CsvDefine csvDefine,
            string strCsvLine)
        {
            using(var ms = new MemoryStream())
            {
                var sw = new StreamWriter(ms);
                sw.Write(strCsvLine);
                ms.Seek(0, SeekOrigin.Begin);
                StreamReader sr = new StreamReader(ms);
                return ReadOneCsvRow(csvDefine, sr);
            }
        }

        #endregion
    }
}
